import base.DeviceBase;
import base.UsersBase;
import base.UsersBaseInterface;
import device.AdminDevice;
import device.Device;
import org.slf4j.LoggerFactory;
import messages.DevMessage;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Objects;

import org.slf4j.Logger;

public class MainServer {
    private final DeviceBase deviceBase;
    private final UsersBaseInterface usersBase;
    private static final Logger logger = LoggerFactory.getLogger(MainServer.class);

    public MainServer() throws SQLException {
        usersBase = new UsersBase();
        deviceBase = new DeviceBase(usersBase);
    }

    public void closeBase() throws SQLException {
        usersBase.close();
    }

    public static void main(String[] args) throws SQLException {
        final MainServer server = new MainServer();
        try {
            server.run();
            server.closeBase();
        } catch (IOException e) {
            server.closeBase();
            throw new RuntimeException(e);
        }
    }

    private void handleAccept(SelectionKey key, Selector selector) throws IOException {
        // 处理新的连接
        SocketChannel clientChannel;
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        // 将新连接注册到 selector
        clientChannel.register(selector, SelectionKey.OP_READ);
        logger.info("获取到新连接: [{}]", clientChannel.getRemoteAddress());
    }

    private boolean handleReadable(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            int bytesRead = clientChannel.read(buffer);
            if (bytesRead == -1) {
                deviceBase.removeDevice(clientChannel);
                clientChannel.close();
                return false;
            }
        } catch (IOException e) {
            logger.warn("读取 I/O 错误，关闭连接 [{}]", clientChannel.getRemoteAddress());
            deviceBase.removeDevice(clientChannel);
            clientChannel.close();
            return false;
        }

        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        // 生成设备消息
        DevMessage devMessage = DevMessage.create(new String(data));
        if (devMessage == null) {
            logger.warn("接收到来自 [{}] 的未知消息", clientChannel.getLocalAddress());
            return false;
        }

        String deviceID;
        Device device;
        switch (devMessage.getMessageType()) {
            case MESSAGE_TYPE_REGISTER:
                if (deviceBase.registerDevice(clientChannel, devMessage) == 0) {
                    logger.info(
                            "注册设备 [{}] 到设备库中。类型：{}",
                            devMessage.getDeviceID(),
                            devMessage.getDeviceType()
                    );
                }
                break;

            case MESSAGE_TYPE_REG_USER:
                deviceID = devMessage.getDeviceID();
                device = deviceBase.getDeviceByID(deviceID);
                String userName = devMessage.getMsgDict("uname");
                String password = devMessage.getMsgDict("passwd");
                if (usersBase.isUserExist(userName)) {
                    logger.warn("注册用户 [{}]，但用户已存在。", userName);
                    device.simpleSend("用户 '" + userName + "' 已存在");
                } else {
                    logger.warn("成功注册用户 [{}]", userName);
                    usersBase.createUser(userName, password);
                    device.simpleSend("success");
                }
                break;

            case MESSAGE_TYPE_PUBLISH:
                deviceID = devMessage.getDeviceID();
                device = deviceBase.getDeviceByID(deviceID);
                HashMap<String, String> msgsSet = devMessage.getMsgDict();
                // 拦截充电桩的 balance 字段信息
                // 拦截智能药箱的 u v x y alarm
                try {
                    device.update(msgsSet);
                    device.publish(devMessage.getMsgDictKeys());
                } catch (IOException e) {
                    logger.warn("设备 [{}] 推送消息失败：写缓冲出错", deviceID);
                }
                break;

            case MESSAGE_TYPE_LOGS:
                logger.debug("msgType=LOG trigger.");
                break;

            case MESSAGE_TYPE_SUBSCRIBE:
                String targetID = devMessage.getMsgDict("tgtID");
                deviceID = devMessage.getDeviceID();
                Device targetDevice = deviceBase.getDeviceByID(targetID);
                device = deviceBase.getDeviceByID(deviceID);
                if (device != null && targetDevice != null) {
                    boolean result =
                            device.subscribe(
                                    targetDevice,
                                    () -> usersBase.verifyUser(
                                            devMessage.getMsgDict("uname"),
                                            devMessage.getMsgDict("passwd")));
                    if (result) {
                        device.simpleSend("success");
                        logger.info("设备 [{}] 成功订阅消息", deviceID);
                    } else {
                        logger.warn("设备 [{}] 无法订阅消息：用户名与密码验证失败。", deviceID);
                    }
                } else {
                    if (device != null) {
                        device.simpleSend("Error: 订阅设备失败，无设备 " + targetID);
                    }
                    logger.warn("设备 [{}] 订阅消息失败：无对应设备 [{}]", deviceID, targetID);
                }
                break;

            case MESSAGE_TYPE_CONTROL:
                deviceID = devMessage.getDeviceID();
                AdminDevice adminDevice = (AdminDevice) deviceBase.getDeviceByID(deviceID);
                if (!adminDevice.comparePassword(devMessage.getMsgDict("passwd"))) {
                    logger.warn("ADMIN 设备 [{}] 密码校验失败", deviceID);
                    break;
                }
                if (Objects.equals(devMessage.getMsgDict("quit"), "yes")) {
                    logger.info("设备 [{}] 指示服务端关闭，已退出。", deviceID);
                    return true;
                }
                break;
        }
        return false;
    }

    public void run() throws IOException {
        boolean willExit = false;
        // 创建多路选择
        Selector selector = Selector.open();
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress(2933));
        serverSocket.configureBlocking(false);
        // accept 事件注册到 selector
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        logger.info("智能充电桩后台系统 v0.1.0 启动，端口号 2933。");

        while (!willExit) {
            // 阻塞并开始监听是否有事件
            selector.select();
            // 事件到达时，组成一队事件列表，并迭代化
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();

                if (key.isAcceptable()) {
                    handleAccept(key, selector);
                } else if (key.isReadable()) {
                    willExit = handleReadable(key);
                } else {
                    logger.debug("触发未知分支");
                }
            }
        }
    }
}
